﻿using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
//using System.Windows.Forms;
using System.Threading;
using System.Net.NetworkInformation;
using System.Net;

namespace Innovox.NetTools
{
    public class NetScan // : Control
    {
        // Fired when each ping completes
        public event EventHandler<NetScanCompletedEventArgs> PingComplete;

        // Fired when all pings complete
        public event EventHandler<EventArgs> NetScanComplete;

        static int _pingTimeout = 20000;
        static public int PingTimeout { get { return _pingTimeout; } set { _pingTimeout = value; } }
        // Limits the number of pings happening simultaniously
        public int ThreadCount { get; set; }
        private Semaphore pingBlock;

        public NetScan()
        {
            ThreadCount = 64;            
        }

        /// Starts ping operations on a background thread.
        /// Contains the starting and ending IP address for the pings.
        
        public void Start(PingRange pr)
        {
            pingBlock = new Semaphore(0, ThreadCount);
            // All items in the semaphore start out locked. This releases them all.
            pingBlock.Release(ThreadCount);

            Thread t = new Thread(new ParameterizedThreadStart(pingWorker_DoWork));
            t.Start(new PingRange(pr.StartRange, pr.EndRange));
        }

        
        /// Loops through the IP address and does the pings.
        /// Start and end IP addresses to ping.

        private void pingWorker_DoWork(object o)
        {

            PingRange pingRange = (PingRange)o;
            //ArrayList ips = new ArrayList();
            // Get the starting and ending address as a byte array to make it easier to
            // loop through.

            byte[] start = pingRange.StartRange.GetAddressBytes();
            byte[] end = pingRange.EndRange.GetAddressBytes();

            // Each thread that's spawned will be put in this list so that it's easy to
            // know when they've all completed.

            LinkedList<Thread> threads = new LinkedList<Thread>();

            // Loop through each octet in the IP address, and ping on a background thread.

            for (byte o0 = start[0]; o0 <= end[0]; o0++)

                for (byte o1 = start[1]; o1 <= end[1]; o1++)

                    for (byte o2 = start[2]; o2 <= end[2]; o2++)

                        for (byte o3 = start[3]; o3 <= end[3]; o3++)
                        {
                            pingBlock.WaitOne();
                            Thread t = new Thread(new ParameterizedThreadStart(DoPing));
                            t.Start(new IPAddress(new byte[] { o0, o1, o2, o3 }));
                            threads.AddLast(t);
                          // ips.Add(new IPAddress(new byte[] { o0, o1, o2, o3 });
                        }

            // Wait until all the pings are done.

            foreach (Thread ln in threads)
            {
                ln.Join();
            }

            // Raise an event saying the pings are done on the main UI thread.
            try
            {
                RaiseNetScanComplete();
            }

            catch (InvalidOperationException)
            {
            }

            catch (NullReferenceException)
            {
            }
        }

        private delegate void RaiseNetScanCompleteHandler();

        /// Fire an event to the client on the same thread that they invoked this
        /// object on so that the UI can be safely updated.

        private void RaiseNetScanComplete()
        {
            if (NetScanComplete != null)
                NetScanComplete(this, new EventArgs());
        }

        public static PingReply Ping(IPAddress ip)
        {
            Random r = new Random();
            Ping ping = new Ping();
            PingReply pr = ping.Send(ip, PingTimeout);
            
            return pr;
            /*if (pr.Status != IPStatus.Success)
            {
                Thread.Sleep(100 + r.Next(300));
                //ping = new Ping();
                pr = ping.Send(ip, PingTimeout);

                if (pr.Status != IPStatus.Success)
                {
                    Thread.Sleep(100 + r.Next(300));
                    //ping = new Ping();
                    pr = ping.Send(ip, PingTimeout);
                }
            }

            //if (pr.Status == IPStatus.Success)
            //    Console.WriteLine(ip.ToString() + " ping");

            return pr;*/
        }

        public static PingReply Ping(IPAddress ip, CancellationSource cancelSource)
        {
            Ping ping = new Ping();
            return ping.Send(ip, NetScan.PingTimeout);

            /*
            int timeout = PingTimeout;
            Random r = new Random();
            Ping ping = new Ping();
            PingReply pr = null;// = ping.Send(ip, PingTimeout);
            
            
            int retries = 0;

            while (retries <= 2)
            {
                pr = ping.Send(ip, timeout);
                if (pr.Status == IPStatus.Success || cancelSource.IsCancelRequest)
                    break;
                retries++;
                Thread.Sleep(100 + r.Next(300));
                if (cancelSource.IsCancelRequest)
                    break;
            }
            return pr;   */       
        }
        /// <summary>
        /// Perform the actual ping, and release the semaphore so that another thread
        /// can go.
        /// </summary>
        /// <param name="o">Ip address</param>
        private void DoPing(object o)
        {
            Random r = new Random();
            IPAddress ip = (IPAddress)o;
            Ping ping = new Ping();
            Thread.Sleep(10 + r.Next(90));
            PingReply pr = ping.Send(ip, PingTimeout);

            pingBlock.Release();

            try
            {
                //this.Invoke(new PingResults(Results), new object[] { pr });
                Results(pr);
            }

            catch (InvalidOperationException)
            {
            }

            catch (NullReferenceException)
            {
            }
        }

        protected delegate void PingResults(PingReply pr);

        /// Fire an event when each ping completes so that the client can show
        /// progress

        /// Ping results

        void Results(PingReply pr)
        {
            if (PingComplete != null)
            {
                NetScanCompletedEventArgs e = new NetScanCompletedEventArgs();
                e.Reply = pr;
                PingComplete(this, e);
            }
        }
    }
}